Aplicar al análisis de clases latentes

Nada más

Autor/a

Andrés González Santa Cruz

Fecha de publicación

31 de dic, 2024

1 Configurar

Código
#eliminar archivos previos y limpiar la memoria del entorno
rm(list=ls());gc()
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  600461 32.1    1372380 73.3   686445 36.7
Vcells 1103661  8.5    8388608 64.0  1876466 14.4
Código
#cargar datos
load("_data/palestine.RData")

#Definir el repositorio sobre el que instalar los paquetes desde Chile
options(repos=structure(c(CRAN="https://cran.dcc.uchile.cl/"))) 

options(install.packages.check.source = "yes") # Chequea la fuente de los paquetes


if(!require(pacman)){install.packages("pacman");require(pacman)}

pacman::p_unlock(lib.loc = .libPaths()) #para no tener problemas reinstalando paquetes

#obliga a desplegarlo en la versión de R 4.4.0
if(Sys.info()["sysname"]=="Windows"){
if (getRversion() != "4.4.0") { stop("Requiere versión de R 4.4.0. Actual: ", getRversion()) }
}

# Cargar o instalar paquetes necesarios
pacman::p_load(
  showtext,   # Para cambiar la fuente de las letras
  ggplot2,    # Para elaborar gráficos
  plotly,     # Para elaborar gráficos interactivos
  magick,     # Para mostrar imágenes
  knitr,      # Para hacer tablas e interactuar con informes
  kableExtra, # Tablas más bonitas
  tidyverse,  # Para manipular bases de datos
  rio,        # Para importar y exportar bases de datos en distintos formatos
  psych,      # Para explorar variables
  parallel,   # Para paralelizar los procesos en la CPU
  doParallel, # Para paralelizar los procesos en la CPU
  glca,       # Para llevar a cabo análisis de clases latentes
  DiagrammeR, # Para generar gráficos esquemáticos
  DiagrammeRsvg, # Para exportar gráficos
  rsvg,       # Para transformar gráficos en formato .svg
  htmlwidgets, # Para visualizarlos en una presentación
  janitor,    # Permite limpiar bases de datos, entre otras funciones
  tableone,
  install = TRUE # Instala los paquetes si no están presentes
)

#para comparaciones
if(!require(chisq.posthoc.test)){devtools::install_github("ebbertd/chisq.posthoc.test")}


2 Describir datos

Código
# Crear un subconjunto de las variables de interés
variables_interes <- c("QKUW40_1", "QKUW40_2", "QKUW40_3", "QKUW40_4", 
                       "QKUW40_5", "QKUW40_90", "QKUW40_97", "QKUW40_98", 
                       "QKUW40_99", "Q1002", "Q1005")

# Generar la tabla descriptiva
tabla_descriptiva <- CreateTableOne(vars = variables_interes, data = arabebarometro_selected, factorVars = variables_interes)

# Mostrar la tabla en consola
tabla_df <- as.data.frame(print(tabla_descriptiva, showAllLevels = TRUE, missing = TRUE))
tabla_df <-cbind.data.frame(var= rownames(tabla_df), tabla_df)
rownames(tabla_df)<-NULL
tabla_df$var <- sub("\\.\\.\\.\\.", "", tabla_df$var)
tabla_df$var <- ifelse(grepl("^X", tabla_df$var), "", tabla_df$var)
Código
tabla_df%>% 
  knitr::kable(caption="Descriptivos", col.names=c("Variable", "Categoría", "Total", "Perdidos"))%>%
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"),font_size = 12) %>% 
  kableExtra::scroll_box(width = "100%", height = "575px") 
Descriptivos
Variable Categoría Total Perdidos
n 1210
QKUW40_1 0 200 (16.5) 0.0
1 1010 (83.5)
QKUW40_2 0 598 (49.4) 0.0
1 612 (50.6)
QKUW40_3 0 950 (78.5) 0.0
1 260 (21.5)
QKUW40_4 0 768 (63.5) 0.0
1 442 (36.5)
QKUW40_5 0 453 (37.4) 0.0
1 757 (62.6)
QKUW40_90 0 1198 (99.0) 0.0
1 12 ( 1.0)
QKUW40_97 0 1176 (97.2) 0.0
1 34 ( 2.8)
QKUW40_98 0 1205 (99.6) 0.0
1 5 ( 0.4)
QKUW40_99 0 1185 (97.9) 0.0
1 25 ( 2.1)
Q1002 1 621 (51.3) 0.0
2 589 (48.7)
Q1005 1 645 (53.3) 0.0
2 46 ( 3.8)
3 277 (22.9)
4 52 ( 4.3)
5 153 (12.6)
6 32 ( 2.6)
99 5 ( 0.4)

3 Modelar

Código
#Definimos el modelo: ítems manifiestos son explicados por una variable latente
f_lca<- item(QKUW40_1, QKUW40_2, QKUW40_3, QKUW40_4, QKUW40_5, QKUW40_90, QKUW40_97, QKUW40_98, QKUW40_99) ~ 1
#Semilla aleatoria para reproducibilidad
seed<-2125
#Opciones de la función
verbose<- F # Seguir los resultados. TRUE= ver proceso de convergencia
init <- 1e2 # puntos iniciales sobre los cuales encontrar una solución global. sugiero 5e2 
testiter <- 5e1 # Iteraciones para encontrar convergencia sugiero 5e2
#Otros parámetros son : eps = 1e-6 (tolerancia)

old <- Sys.time()

lca02 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 2, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca03 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 3, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca04 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 4, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca05 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 5, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca06 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 6, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca07 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 7, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca08 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 8, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca09 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 9, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)
lca10 <- glca(f_lca, data = arabebarometro_selected[,paste0("QKUW40_", c(1:5, 90, 97, 98, 99))], nclass= 10, seed= seed, verbose= verbose, n.init= init, decreasing=T, maxiter=1e4, testiter = testiter)

new_med<-(Sys.time())
paste0("El modelo tomó ",round(new_med-old,2),"segundos hasta que todos los análisis fueron computados")
[1] "El modelo tomó 32.14segundos hasta que todos los análisis fueron computados"
Código
##| column: page
#| fig-dpi: 600

#para obtener otros índices de ajuste
gof<-
gofglca(lca02, lca03, lca04, lca05, lca06, lca07, lca08, lca09, lca10, test = "chisq")

#número total de clases latentes postuladas
max_classes<- nrow(gof$gtable)+1

#para obtener índice BLRT= Evaluar si el modelo k presenta mejor ajuste que el k-1

#número de iteraciones para bootstrap, se recomienda 5e2
nboot= 10
bootlrt<- gofglca(lca02, lca03, lca04, lca05, lca06, lca07, lca08, lca09, lca10, test = "boot", nboot= nboot, seed=2125)

manualcolors <- rev(c('gray20', 'gray50', 'gray80')) # Tres colores en escala de grises
manual_linetypes <- rev(c('solid', 'dashed', 'dotted')) # Tres estilos de línea
levels <- c("logLik", "Gsq", "AIC", "CAIC", "BIC", "entropy", "Res.Df")
labels <- c('Log-Verosimilitud', 'Chi2', 'Criterio de Información\nde Akaike (AIC)', 
            'AIC Corregido', 'Criterio de Información\nBayesiano (BIC)', 'Entropía', 
            'Grados de libertad residuales')

fig_lca_fit <- cbind.data.frame(rn = 2:max_classes, gof$gtable) %>%
  data.frame() %>% 
  dplyr::mutate_if(is.character, as.numeric) %>%  # Convertir columnas de carácter a numérico
  #formatear la base de manera larga (es decir), cada fila es la combinación única de una probabilidad, pregunta y clase
  tidyr::pivot_longer(cols = -rn, names_to = "indices", values_to = "value", values_drop_na = F) %>%
  #dejar en formato factor los índices, para reconocerlos  e identificarlos
  dplyr::mutate(indices = factor(indices, levels = levels, labels = labels)) %>%
  # Filtrar solo AIC y BIC
  dplyr::filter(grepl("(AIC|BIC)", indices, ignore.case = TRUE)) %>%
  dplyr::mutate(ModelIndex = factor(rn, levels = 2:max_classes)) %>% 
  ggplot(aes(x = ModelIndex, y = value, group = indices, 
             color = indices, linetype = indices)) +
  geom_line(linewidth = 1.5) +
  # Colores y estilos de línea personalizados
  scale_color_manual(values = manualcolors) +
  scale_linetype_manual(values = manual_linetypes) +
  labs(x = "Número de clases", y = "Valor", color = "Medida", linetype = "Medida") +
  theme_bw()

fig_lca_fit

#Para exportar gráfico
ggsave("_figs/_fig2_comparison_glca.png",fig_lca_fit, dpi=600)
Índices de ajuste modelo clases latentes

Índices de ajuste modelo clases latentes

Vemos un resumen de la salida del modelo con mejores índices de ajuste BIC.

Código
#combinamos
cbind.data.frame(rn=2:max_classes,gof$gtable, blrt= bootlrt$gtable[, "Boot p-value"]) %>%#
  dplyr::mutate(blrt= ifelse(as.numeric(blrt)<.001, "<0.001",sprintf("%1.3f",blrt))) %>% 
  dplyr::select(rn, everything()) %>% 
    knitr::kable(format="markdown", caption="Índices de ajuste modelos") %>% 
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"),font_size = 12) %>% 
  kableExtra::scroll_box(width = "100%", height = "375px")  
Índices de ajuste modelos
rn logLik AIC CAIC BIC entropy Res.Df Gsq blrt
2 -3768.290 7574.579 7690.449 7671.449 0.5091911 492 521.20987 <0.001
3 -3623.139 7304.277 7481.130 7452.130 0.8260667 482 230.90751 <0.001
4 -3597.998 7273.996 7511.833 7472.833 0.8642603 472 180.62667 <0.001
5 -3581.126 7260.252 7559.072 7510.072 0.8836223 462 146.88204 <0.001
6 -3568.669 7255.339 7615.143 7556.143 0.8286448 452 121.96915 <0.001
7 -3557.422 7252.843 7673.631 7604.631 0.8556557 442 99.47376 <0.001
8 -3551.824 7261.649 7743.420 7664.420 0.8492723 432 88.27899 <0.001
9 -3540.604 7259.207 7801.962 7712.962 0.8876687 422 65.83743 <0.001
10 -3534.302 7266.604 7870.343 7771.343 0.8748119 412 53.23469 <0.001
Código
mejor_modelo<-
as.numeric(cbind.data.frame(rn=2:max_classes,gof$gtable) %>% dplyr::summarise(which.min(BIC)+1))
Código
summary(
  eval(parse(text = paste0("lca",sprintf("%02.0f",mejor_modelo))))
        ) #

Call:
glca(formula = f_lca, data = arabebarometro_selected[, paste0("QKUW40_", 
    c(1:5, 90, 97, 98, 99))], nclass = 3, n.init = init, decreasing = T, 
    testiter = testiter, maxiter = 10000, seed = seed, verbose = verbose)

Manifest items : QKUW40_1 QKUW40_2 QKUW40_3 QKUW40_4 QKUW40_5 QKUW40_90 QKUW40_97 QKUW40_98 QKUW40_99 

Categories for manifest items :
          Y = 1 Y = 2
QKUW40_1      0     1
QKUW40_2      0     1
QKUW40_3      0     1
QKUW40_4      0     1
QKUW40_5      0     1
QKUW40_90     0     1
QKUW40_97     0     1
QKUW40_98     0     1
QKUW40_99     0     1

Model : Latent class analysis 

Number of latent classes : 3 
Number of observations : 1210 
Number of parameters : 29 

log-likelihood : -3623.139 
     G-squared : 230.9075 
           AIC : 7304.277 
           BIC : 7452.13 

Marginal prevalences for latent classes :
Class 1 Class 2 Class 3 
0.05343 0.74220 0.20437 

Class prevalences by group :
    Class 1 Class 2 Class 3
ALL 0.05343  0.7422 0.20437

Item-response probabilities (Y = 1) :
        QKUW40_1 QKUW40_2 QKUW40_3 QKUW40_4 QKUW40_5 QKUW40_90 QKUW40_97
Class 1   1.0000   1.0000   1.0000   1.0000   0.9710    0.8953    0.5050
Class 2   0.1395   0.5671   0.9095   0.7506   0.4046    0.9964    1.0000
Class 3   0.0406   0.0972   0.2773   0.1183   0.1086    0.9920    0.9919
        QKUW40_98 QKUW40_99
Class 1    0.9381    0.6752
Class 2    1.0000    1.0000
Class 3    0.9960    0.9838

Item-response probabilities (Y = 2) :
        QKUW40_1 QKUW40_2 QKUW40_3 QKUW40_4 QKUW40_5 QKUW40_90 QKUW40_97
Class 1   0.0000   0.0000   0.0000   0.0000   0.0290    0.1047    0.4950
Class 2   0.8605   0.4329   0.0905   0.2494   0.5954    0.0036    0.0000
Class 3   0.9594   0.9028   0.7227   0.8817   0.8914    0.0080    0.0081
        QKUW40_98 QKUW40_99
Class 1    0.0619    0.3248
Class 2    0.0000    0.0000
Class 3    0.0040    0.0162
Código
glca_mejor_ajuste<-eval(parse(text = paste0("lca",sprintf("%02.0f",mejor_modelo))))


4 Analisis

Código
##| column: page
#| fig-dpi: 600

#https://rdrr.io/cran/glca/src/R/plot.glca.R
plot(
     glca_mejor_ajuste
     , ask=F)
Modelo seleccionado

Modelo seleccionado

Modelo seleccionado

Modelo seleccionado

Vemos la entropía usando glca_mejor_ajuste$gof$entropy 0.83. Bastante buena.

Ahora veremos los perfiles de las probabilidades de respuesta a los ítems condicionales a la pertenencia a una determinada clase.

Código
##| column: page
#| fig-dpi: 600
# Configuración inicial y preparación de datos
# Extracción de parámetros rho del modelo GLCA
rho_glca <- do.call("bind_rows", glca_mejor_ajuste$param$rho$ALL) %>%
  t() %>%
  round(2) %>%
  data.table::data.table(keep.rownames = TRUE) %>%
  magrittr::set_colnames(c("variables", paste0("Class", 1:glca_mejor_ajuste$model$C))) %>%
  tidyr::separate(variables, into = c("var", "prob"), sep = ".Y =")

# Transformación de datos del modelo
lcmodel_glca <- rho_glca %>%
  tidyr::pivot_longer(
    cols = 3:5,
    names_to = "class",
    values_to = "value"
  ) 

# Unión de etiquetas con probabilidades del modelo
lcmodel_glca <- lcmodel_glca %>%
  dplyr::mutate(pr = as.numeric(gsub("[^0-9.]+", "", prob))-1) %>% 
  dplyr::mutate(etiqueta= dplyr::case_when(
    grepl("_1$", var) ~ "01.Boicot\nempresas", 
    grepl("_2$", var) ~ "02.Seguir\nnoticias", 
    grepl("_3$", var) ~ "03.Actividades\nsolidaridad",
    grepl("_4$", var) ~ "04.Difusión\nsolidaridad",
    grepl("_5$", var) ~ "05.Donaciones\nmonetarias",
    grepl("_90$", var) ~ "90.Otro\n(Especificar)",
    grepl("_97$", var) ~ "97.No he tomado\nninguna acción",
    grepl("_98$", var) ~ "98.No sabe",
    grepl("_99$", var) ~ "99.Se niega\nresponder",
    TRUE ~ NA_character_  # Para manejar valores no coincidentes
  ))

# Generación de etiquetas para gráficos
lcmodel_glca$text_label <- paste0(
  "Etiqueta:", lcmodel_glca$etiqueta,
  "<br>Categoría:", lcmodel_glca$pr,
  "<br>%: ", scales::percent(lcmodel_glca$value)
)
#Ordenamos las opciones por orden de aparición
lcmodel_glca$var <- factor(lcmodel_glca$var, levels = paste0("QKUW40_", c(1:5, 90, 97, 98, 99)))

# Cálculo de porcentajes de clase, >.5 PROBABILIDAD DE PERTENENCIA
glca_pop_perc_05 <- glca_mejor_ajuste$posterior$ALL %>%
  dplyr::mutate_all(~ ifelse(. > .5, 1, 0)) %>%
  dplyr::mutate(final_05 = dplyr::case_when(
    `Class 1` == 1 ~ 1, `Class 2` == 1 ~ 2, `Class 3` == 1 ~ 3
  )) %>%
  janitor::tabyl(final_05) %>%
  dplyr::mutate(
    percent = scales::percent(percent, accuracy = .1),
    class = paste0("Clase", final_05),
    print = paste0(class, "\n(n=", n, ";", percent, ")")
  )
# Cálculo de porcentajes de clase, >.7 PROBABILIDAD DE PERTENENCIA
glca_pop_perc_07 <- glca_mejor_ajuste$posterior$ALL %>%
  dplyr::mutate_all(~ ifelse(. > .7, 1, 0)) %>%
  dplyr::mutate(final_07 = dplyr::case_when(
    `Class 1` == 1 ~ 1, `Class 2` == 1 ~ 2, `Class 3` == 1 ~ 3
  )) %>%
  janitor::tabyl(final_07) %>%
  dplyr::mutate(
    percent = scales::percent(percent, accuracy = .1),
    class = paste0("Clase", final_07),
    print_07 = paste0(class, "[>.7 prob]","\n(n=", n, ";", percent, ")")
  ) %>% 
  #sacamos sujetos no clasificados
  dplyr::filter(!is.na(final_07)) %>% 
  #seleccionamos columnas de interés para unir y no generar redundancia
  dplyr::select(final_07, class, print_07)

# Unión con datos transformados
lcmodel_glca <- lcmodel_glca %>%
  dplyr::mutate(class=gsub("Class","Clase",class)) %>% 
  left_join(glca_pop_perc_05, by = c("class")) %>%
  left_join(glca_pop_perc_07, by = c("class")) %>%
  dplyr::select(-class) %>%
  dplyr::rename("class" = "print") 

# Gráfico principal
zp <- ggplot(dplyr::mutate(lcmodel_glca, class=gsub("\n","",class)), aes(x = var, y = value, fill = factor(pr), label = text_label)) +
  geom_bar(stat = "identity", position = "stack") +
  facet_grid(class ~ .) +
  scale_fill_brewer(type = "seq", palette = "Greys", na.value = "white") +
  theme_bw() +
  labs(
    y = "Porcentaje de probabilidad de respuesta",
    x = "",
    fill = "Categorías de\nRespuesta"
  ) +
  theme(
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    panel.grid.major.y = element_blank(),
    axis.text.x = element_text(angle = 30, hjust = 1)
  ) +
  guides(fill = guide_legend(reverse = TRUE))

# Exportación del gráfico y datos
# ggplotly(zp, tooltip = c("text_label")) %>%
#   layout(xaxis = list(showticklabels = TRUE), height = 600, width = 1000)
ggplotly(zp, tooltip = c("text_label")) %>%
  layout(
    legend = list(
      orientation = "h", # Leyenda horizontal
      x = 0.5,           # Centra horizontalmente
      y = -0.2           # Mueve la leyenda debajo del gráfico
    ),
    xaxis = list(showticklabels = TRUE),
    height = 700, # Ajusta la altura
    width = 700, # Ajusta el ancho
    margin = list(l = 100, r = 50, t = 50, b = 150) # Márgenes para etiquetas y títulos
  )

#EXPORTAMOS EL GRÁFICO NO-INTERACTIVO
ggsave("_figs/_fig4_LCA_patrones_respuesta.png", zp, dpi = 600)

Probabilidades de respuesta esperada por cada categoría en cada ítem según Clase estimada

Código
#Exportar las tablas obtenidas
lcmodel_glca %>% 
  #excluimos columnas de procesos intermedios que pueden confundirnos
  dplyr::select(-any_of(c("text_label", "prob", "n", "percent", "final_05", "final_07"))) %>%
  #sacamos información redundante
  #utilizamos doble escape (\\) para sacar símbolos que pueden ser interpretados literalmente
  dplyr::mutate(print_07 = gsub("\\[>\\.7 prob\\]\\n", " ", print_07))%>%
  dplyr::mutate((across(c("etiqueta", "class"),~gsub("\n", " ",.))))%>%
  #renombramos columnas para mayor claridad
  dplyr::rename("Prob. de pertencer a clase [P>.5]"="class", "Prob. de pertencer a clase [P>.7]"="print_07") %>% 
  {
  rio::export(.,"_output/variables_probabilidades_respuesta.xlsx")
  rio::export(.,"_output/variables_probabilidades_respuesta.csv")
  knitr::kable(., caption= "Probabilidades de respuesta de cada clase y clasificaciones por según distintos criterios de clasificación por probabilidad posterior de pertenencia", escape=F)
  }
Probabilidades de respuesta de cada clase y clasificaciones por según distintos criterios de clasificación por probabilidad posterior de pertenencia
var value pr etiqueta Prob. de pertencer a clase [P>.5] Prob. de pertencer a clase [P>.7]
QKUW40_1 1.00 0 01.Boicot empresas Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_1 0.14 0 01.Boicot empresas Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_1 0.04 0 01.Boicot empresas Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_1 0.00 1 01.Boicot empresas Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_1 0.86 1 01.Boicot empresas Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_1 0.96 1 01.Boicot empresas Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_2 1.00 0 02.Seguir noticias Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_2 0.57 0 02.Seguir noticias Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_2 0.10 0 02.Seguir noticias Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_2 0.00 1 02.Seguir noticias Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_2 0.43 1 02.Seguir noticias Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_2 0.90 1 02.Seguir noticias Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_3 1.00 0 03.Actividades solidaridad Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_3 0.91 0 03.Actividades solidaridad Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_3 0.28 0 03.Actividades solidaridad Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_3 0.00 1 03.Actividades solidaridad Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_3 0.09 1 03.Actividades solidaridad Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_3 0.72 1 03.Actividades solidaridad Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_4 1.00 0 04.Difusión solidaridad Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_4 0.75 0 04.Difusión solidaridad Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_4 0.12 0 04.Difusión solidaridad Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_4 0.00 1 04.Difusión solidaridad Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_4 0.25 1 04.Difusión solidaridad Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_4 0.88 1 04.Difusión solidaridad Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_5 0.97 0 05.Donaciones monetarias Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_5 0.40 0 05.Donaciones monetarias Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_5 0.11 0 05.Donaciones monetarias Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_5 0.03 1 05.Donaciones monetarias Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_5 0.60 1 05.Donaciones monetarias Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_5 0.89 1 05.Donaciones monetarias Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_90 0.90 0 90.Otro (Especificar) Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_90 1.00 0 90.Otro (Especificar) Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_90 0.99 0 90.Otro (Especificar) Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_90 0.10 1 90.Otro (Especificar) Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_90 0.00 1 90.Otro (Especificar) Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_90 0.01 1 90.Otro (Especificar) Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_97 0.51 0 97.No he tomado ninguna acción Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_97 1.00 0 97.No he tomado ninguna acción Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_97 0.99 0 97.No he tomado ninguna acción Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_97 0.49 1 97.No he tomado ninguna acción Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_97 0.00 1 97.No he tomado ninguna acción Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_97 0.01 1 97.No he tomado ninguna acción Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_98 0.94 0 98.No sabe Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_98 1.00 0 98.No sabe Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_98 1.00 0 98.No sabe Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_98 0.06 1 98.No sabe Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_98 0.00 1 98.No sabe Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_98 0.00 1 98.No sabe Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_99 0.68 0 99.Se niega responder Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_99 1.00 0 99.Se niega responder Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_99 0.98 0 99.Se niega responder Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)
QKUW40_99 0.32 1 99.Se niega responder Clase1 (n=64;5.3%) Clase1 (n=64;5.3%)
QKUW40_99 0.00 1 99.Se niega responder Clase2 (n=946;78.2%) Clase2 (n=856;70.7%)
QKUW40_99 0.02 1 99.Se niega responder Clase3 (n=200;16.5%) Clase3 (n=152;12.6%)


A partir del gráfico anterior y la tabla, podemos interpretar las clases:

Clase 1= Describe a personas que no han tomado ninguna acción concreta, no saben o no responden.

Clase 2= Describe a personas que han actuado de forma individual e internalizante.

Clase 3= Describe a personas que han actuado de muchas formas, incluidas acciones colectivas, mobilizaciones y difusión.

De ahí uno podría hacer interpretaciones en contraste con teorías de movimientos sociales, advocacy groups, etc. Por ejemplo, grupos que reflejan actitudes de desmovilización o desconexión, grupos de acciones individuales, simbólicas o de bajo costo (“participación selectiva” / free-riding), y un grupo cuya movilización tiende a ser colectiva, incorporando recursos y redes con mayor nivel de compromiso y participación activa.


Código
posterior_glca_05<-
glca_mejor_ajuste$posterior$ALL %>% 
    dplyr::mutate_all(~ifelse(.>.5,1,0)) %>% 
  dplyr::mutate(final_05=dplyr::case_when(`Class 1`==1~1,`Class 2`==1~2, `Class 3`==1~3))

posterior_glca_07<-
glca_mejor_ajuste$posterior$ALL %>% 
    dplyr::mutate_all(~ifelse(.>.7,1,0)) %>% 
  dplyr::mutate(final_07=dplyr::case_when(`Class 1`==1~1,`Class 2`==1~2, `Class 3`==1~3))


glca_mejor_ajuste$posterior$ALL %>%
  dplyr::mutate_all(~ ifelse(. > .5, 1, 0)) %>%
  dplyr::mutate(final_05 = dplyr::case_when(
    `Class 1` == 1 ~ 1, `Class 2` == 1 ~ 2, `Class 3` == 1 ~ 3
  )) %>% 
  dplyr::mutate(final_07= posterior_glca_07$final_07) %>% 
  janitor::tabyl(final_05, final_07) %>% 
  dplyr::rename("No clasificado" = "NA_") %>% 
  knitr::kable(
    col.names = c("Clases", "Clase 1", "Clase 2", "Clase 3", "No clasificado"),
    caption = "Clasificación por valores superiores a 0,7 en probabilidad vs. 0,5"
  )
Clasificación por valores superiores a 0,7 en probabilidad vs. 0,5
Clases Clase 1 Clase 2 Clase 3 No clasificado
1 64 0 0 0
2 0 856 0 90
3 0 0 152 48
Código
posterior_glca_07 %>% 
    rowwise() %>%
  dplyr::mutate(count_ones = sum(c_across(starts_with("Class")) == 1)) %>%
  ungroup() %>% 
  janitor::tabyl(final_07,count_ones) %>% 
  knitr::kable(col.names= c("Clasificación por valores superiores a 0,7 en probabilidad", "0", "1"), caption="Pruebas por probabilidades de clasificación posterior")
Pruebas por probabilidades de clasificación posterior
Clasificación por valores superiores a 0,7 en probabilidad 0 1
1 0 64
2 0 856
3 0 152
NA 138 0

Vemos que hay algunas observaciones que si clasificáramos por un criterio más estricto de pertenencia (al menos 49% de la variabilidad es explicada por la clase) no alcanzan a pertenecer a una determinada clase.


5 Caracterización/ Comparación con otras variables

5.1 Clasificar-analizar

Código
invisible("Ocupación (Q1005)")
cbind.data.frame(ocupacion=arabebarometro_selected$Q1005, clasif_05= posterior_glca_05$final_05) %>%
    dplyr::mutate(ocupacion= dplyr::case_when(ocupacion%in%c("1","2")~"activa", ocupacion=="5"~"estudiante", T~"inactiva"))%>%
    dplyr::group_by(ocupacion, clasif_05)%>% 
    dplyr::count() %>% 
    dplyr::mutate(clasif_05= dplyr::case_when(clasif_05=="1"~"Clase1",clasif_05=="2"~"Clase2", clasif_05=="3"~"Clase3", T~NA_character_))%>%
    tidyr::pivot_wider(names_from = clasif_05, values_from= n)%>%
  dplyr::mutate(total= Clase1+Clase2+Clase3)%>%
  dplyr::mutate(Clase1= glue::glue("{Clase1}({scales::percent(Clase1/total)})"))%>%
  dplyr::mutate(Clase2= glue::glue("{Clase2}({scales::percent(Clase2/total)})"))%>%
  dplyr::mutate(Clase3= glue::glue("{Clase3}({scales::percent(Clase3/total)})"))

cbind.data.frame(ocupacion= arabebarometro_selected$Q1005, clasif_05= posterior_glca_05$final_05) %>%
    dplyr::mutate(ocupacion= dplyr::case_when(ocupacion%in%c("1","2")~"activa", ocupacion=="5"~"estudiante", T~"inactiva"))%>%
    janitor::tabyl(ocupacion, clasif_05)%>%
    janitor::chisq.test()

#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_
#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_#_

invisible("Sexo (Q1002)")
cbind.data.frame(sexo=arabebarometro_selected$Q1002, clasif_05= posterior_glca_05$final_05) %>%
  dplyr::group_by(sexo, clasif_05)%>% 
  dplyr::count() %>% 
  dplyr::mutate(sexo= ifelse(sexo=="1","hombre", "mujer"))%>%
    dplyr::mutate(clasif_05= dplyr::case_when(clasif_05=="1"~"Clase1",clasif_05=="2"~"Clase2", clasif_05=="3"~"Clase3", T~NA_character_))%>%
  tidyr::pivot_wider(names_from = clasif_05, values_from= n)%>%
  dplyr::mutate(total= Clase1+Clase2+Clase3)%>%
  dplyr::mutate(Clase1= glue::glue("{Clase1}({scales::percent(Clase1/total)})"))%>%
  dplyr::mutate(Clase2= glue::glue("{Clase2}({scales::percent(Clase2/total)})"))%>%
  dplyr::mutate(Clase3= glue::glue("{Clase3}({scales::percent(Clase3/total)})"))

chisq.test(arabebarometro_selected$Q1002, posterior_glca_05$final_05) 

chisq.posthoc.test(table(arabebarometro_selected$Q1002,posterior_glca_05$final_05))

cbind.data.frame(sexo=arabebarometro_selected$Q1002, clasif_07= posterior_glca_07$final_07) %>%
  dplyr::group_by(sexo, clasif_07) %>%
  dplyr::count() %>% 
  dplyr::mutate(sexo= ifelse(sexo=="1","hombre", "mujer"))%>%
  dplyr::mutate(clasif_07= dplyr::case_when(clasif_07=="1"~"Clase1",clasif_07=="2"~"Clase2", clasif_07=="3"~"Clase3", T~NA_character_))%>%
  tidyr::pivot_wider(names_from = clasif_07, values_from= n)%>%
  dplyr::mutate(total= Clase1+Clase2+Clase3)%>%
  dplyr::mutate(Clase1= glue::glue("{Clase1}({scales::percent(Clase1/total)})"))%>%
  dplyr::mutate(Clase2= glue::glue("{Clase2}({scales::percent(Clase2/total)})"))%>%
  dplyr::mutate(Clase3= glue::glue("{Clase3}({scales::percent(Clase3/total)})"))

#excluimos los casos perdidos
chisq.test(table(arabebarometro_selected$Q1002[!is.na(posterior_glca_07$final_07)],posterior_glca_07$final_07[!is.na(posterior_glca_07$final_07)]))

#obtenemos los resultados
chisq.posthoc.test(table(arabebarometro_selected$Q1002[!is.na(posterior_glca_07$final_07)],posterior_glca_07$final_07[!is.na(posterior_glca_07$final_07)]))
# A tibble: 3 × 5
# Groups:   ocupacion [3]
  ocupacion  Clase1 Clase2   Clase3   total
  <chr>      <glue> <glue>   <glue>   <int>
1 activa     30(4%) 543(79%) 118(17%)   691
2 estudiante 9(6%)  104(68%) 40(26%)    153
3 inactiva   25(7%) 299(82%) 42(11%)    366

    Pearson's Chi-squared test

data:  .
X-squared = 19.887, df = 4, p-value = 0.0005256

# A tibble: 2 × 5
# Groups:   sexo [2]
  sexo   Clase1 Clase2   Clase3   total
  <chr>  <glue> <glue>   <glue>   <int>
1 hombre 27(4%) 488(79%) 106(17%)   621
2 mujer  37(6%) 458(78%) 94(16%)    589

    Pearson's Chi-squared test

data:  arabebarometro_selected$Q1002 and posterior_glca_05$final_05
X-squared = 2.3893, df = 2, p-value = 0.3028

  Dimension     Value         1          2          3
1         1 Residuals -1.502353  0.3468845  0.5195646
2         1  p values  0.798000  1.0000000  1.0000000
3         2 Residuals  1.502353 -0.3468845 -0.5195646
4         2  p values  0.798000  1.0000000  1.0000000
# A tibble: 2 × 6
# Groups:   sexo [2]
  sexo   Clase1 Clase2   Clase3   `NA` total
  <chr>  <glue> <glue>   <glue>  <int> <int>
1 hombre 27(5%) 438(80%) 83(15%)    73   548
2 mujer  37(7%) 418(80%) 69(13%)    65   524

    Pearson's Chi-squared test

data:  table(arabebarometro_selected$Q1002[!is.na(posterior_glca_07$final_07)],     posterior_glca_07$final_07[!is.na(posterior_glca_07$final_07)])
X-squared = 2.7833, df = 2, p-value = 0.2487

  Dimension     Value         1          2          3
1         1 Residuals -1.474144  0.0636584  0.9280558
2         1  p values  0.842700  1.0000000  1.0000000
3         2 Residuals  1.474144 -0.0636584 -0.9280558
4         2  p values  0.842700  1.0000000  1.0000000

5.2 Modelo con covariable

Código
f_lca_adj<- item(QKUW40_1, QKUW40_2, QKUW40_3, QKUW40_4, QKUW40_5, QKUW40_90, QKUW40_97, QKUW40_98, QKUW40_99) ~ Q1002 + Q1005
lca03_adj <- glca(f_lca_adj, data = arabebarometro_selected[,c(paste0("QKUW40_", c(1:5, 90, 97, 98, 99)),"Q1002", "Q1005")]%>% dplyr::mutate(Q1005= dplyr::case_when(Q1005%in%c("1","2")~"activa", Q1005=="5"~"estudiante", T~"inactiva")), 
    nclass= 3, 
    seed= seed, 
    verbose= verbose, 
    n.init= init, 
    decreasing=T, 
    maxiter=1e4, 
    testiter = testiter)
Código
lca03_adj$coefficient$ALL
$`Class1/3`
                Odds Ratio Coefficient  Std. Error    t value     Pr(>|t|)
(Intercept)     11.3172196   2.4263254   0.5664119  4.2836768 1.988565e-05
Q1002            0.5791354  -0.5462190   0.3262480 -1.6742447 9.434888e-02
Q1005estudiante  1.1842732   0.1691293   0.4376050  0.3864884 6.992051e-01
Q1005inactiva    0.3382201  -1.0840585   0.3628500 -2.9876214 2.869980e-03

$`Class2/3`
                Odds Ratio Coefficient  Std. Error   t value     Pr(>|t|)
(Intercept)     27.7086236   3.3217437   0.5102814  6.509631 1.113777e-10
Q1002            0.7200225  -0.3284728   0.2930988 -1.120690 2.626491e-01
Q1005estudiante  0.6121956  -0.4907034   0.4123701 -1.189959 2.343032e-01
Q1005inactiva    0.7080095  -0.3452977   0.2970773 -1.162316 2.453434e-01
  • Podría decirse que hombres empleados (intercepto) tienen mayores chances de pertenecer a la clase 1 vs. la 3 (OR= 11.3), y 2 vs. 3 (OR= 27.7)
  • Encontrarse inactivo (retirado, dueñ@ de casa o desempleado y buscando trabajo vs. empleados) se asocia a menores chances de pertenecer a la clase 1 vs. la 3.
  • No hay evidencia de una asociación entre pertenecer a una clase y el sexo.

Tener en cuenta la escasez de observaciones para algunas categorías de respuesta.

Código
# Crear un data frame combinado con ocupación, clasificación y sexo
data_combined <- cbind.data.frame(
    ocupacion = arabebarometro_selected$Q1005,
    clasif_05 = posterior_glca_05$final_05,
    sexo = arabebarometro_selected$Q1002
) %>%
    # Reemplazar valores en la variable 'ocupacion'
    mutate(
        ocupacion = case_when(
            ocupacion %in% c("1", "2") ~ "activa",
            ocupacion == "5" ~ "estudiante",
            TRUE ~ "inactiva"
        )
    )

# Crear tabla de frecuencias cruzadas con xtabs
tabla_cruzada <- xtabs(~ clasif_05 + ocupacion + sexo, data = data_combined)

6 Información de la sesión

Código
#guardamos la información generada
save.image("_data/palestine2.RData")

#vemos desde donde se producen las librerías
message(paste0("R library: ", Sys.getenv("R_LIBS_USER")))
R library: C:\Users\andre\AppData\Local/R/win-library/4.4
Código
message(paste0("Date: ",withr::with_locale(new = c('LC_TIME' = 'C'), code =Sys.time())))
Date: 2024-12-31 20:54:54.581154
Código
message(paste0("Editor context: ", getwd()))
Editor context: H:/Mi unidad/palestine/palestine
Código
#plataforma donde se desplegó el código
devtools::session_info()$platform
 setting  value
 version  R version 4.4.0 (2024-04-24 ucrt)
 os       Windows 11 x64 (build 26100)
 system   x86_64, mingw32
 ui       RTerm
 language (EN)
 collate  Spanish_Chile.utf8
 ctype    Spanish_Chile.utf8
 tz       America/Santiago
 date     2024-12-31
 pandoc   3.2 @ C:/Program Files/RStudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
Código
sesion_info <- devtools::session_info()
dplyr::select(
  tibble::as_tibble(sesion_info$packages),
  c(package, loadedversion, source)
) %>% 
 knitr::kable(caption = "Información paquetes de R", format = "html",
      col.names = c("Paquete", "Versión", "Fuente"),
    row.names = FALSE,
      align = c("c", "l", "r")) %>% 
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"),font_size = 12) %>% 
  kableExtra::scroll_box(width = "100%", height = "375px")  
Información paquetes de R
Paquete Versión Fuente
cachem 1.1.0 CRAN (R 4.4.0)
chisq.posthoc.test 0.1.3 Github (ebbertd/chisq.posthoc.test@186d2ca6bbdba9fc19601aff4696ae1b85e7e0b0)
cli 3.6.3 CRAN (R 4.4.1)
codetools 0.2-20 CRAN (R 4.4.0)
colorspace 2.1-0 CRAN (R 4.4.0)
crosstalk 1.2.1 CRAN (R 4.4.1)
curl 6.0.1 CRAN (R 4.4.2)
data.table 1.15.4 CRAN (R 4.4.0)
DBI 1.2.3 CRAN (R 4.4.0)
devtools 2.4.5 CRAN (R 4.4.1)
DiagrammeR 1.0.11 CRAN (R 4.4.0)
DiagrammeRsvg 0.1 CRAN (R 4.4.1)
digest 0.6.37 CRAN (R 4.4.0)
doParallel 1.0.17 CRAN (R 4.4.0)
dplyr 1.1.4 CRAN (R 4.4.0)
ellipsis 0.3.2 CRAN (R 4.4.0)
evaluate 1.0.1 CRAN (R 4.4.2)
fansi 1.0.6 CRAN (R 4.4.0)
farver 2.1.2 CRAN (R 4.4.0)
fastmap 1.2.0 CRAN (R 4.4.0)
forcats 1.0.0 CRAN (R 4.4.0)
foreach 1.5.2 CRAN (R 4.4.0)
fs 1.6.5 CRAN (R 4.4.2)
generics 0.1.3 CRAN (R 4.4.0)
ggplot2 3.5.1 CRAN (R 4.4.0)
glca 1.4.0 CRAN (R 4.4.1)
glue 1.8.0 CRAN (R 4.4.2)
gtable 0.3.5 CRAN (R 4.4.0)
haven 2.5.4 CRAN (R 4.4.0)
hms 1.1.3 CRAN (R 4.4.0)
htmltools 0.5.8 CRAN (R 4.4.0)
htmlwidgets 1.6.4 CRAN (R 4.4.0)
httpuv 1.6.15 CRAN (R 4.4.0)
httr 1.4.7 CRAN (R 4.4.0)
iterators 1.0.14 CRAN (R 4.4.0)
janitor 2.2.0 CRAN (R 4.4.0)
jsonlite 1.8.9 CRAN (R 4.4.2)
kableExtra 1.4.0 CRAN (R 4.4.1)
knitr 1.49 CRAN (R 4.4.2)
labeling 0.4.3 CRAN (R 4.4.0)
labelled 2.13.0 CRAN (R 4.4.0)
later 1.4.1 CRAN (R 4.4.2)
lattice 0.22-6 CRAN (R 4.4.0)
lazyeval 0.2.2 CRAN (R 4.4.0)
lifecycle 1.0.4 CRAN (R 4.4.0)
lubridate 1.9.3 CRAN (R 4.4.0)
magick 2.8.4 CRAN (R 4.4.1)
magrittr 2.0.3 CRAN (R 4.4.0)
MASS 7.3-60.2 local
Matrix 1.7-0 CRAN (R 4.4.0)
memoise 2.0.1 CRAN (R 4.4.0)
mime 0.12 CRAN (R 4.4.0)
miniUI 0.1.1.1 CRAN (R 4.4.0)
mitools 2.4 CRAN (R 4.4.0)
mnormt 2.1.1 CRAN (R 4.4.0)
munsell 0.5.1 CRAN (R 4.4.0)
nlme 3.1-164 CRAN (R 4.4.0)
pacman 0.5.1 CRAN (R 4.4.1)
pillar 1.9.0 CRAN (R 4.4.0)
pkgbuild 1.4.5 CRAN (R 4.4.2)
pkgconfig 2.0.3 CRAN (R 4.4.0)
pkgload 1.4.0 CRAN (R 4.4.2)
plotly 4.10.4 CRAN (R 4.4.1)
profvis 0.4.0 CRAN (R 4.4.2)
promises 1.3.2 CRAN (R 4.4.2)
psych 2.4.3 CRAN (R 4.4.0)
purrr 1.0.2 CRAN (R 4.4.0)
R.methodsS3 1.8.2 CRAN (R 4.4.0)
R.oo 1.26.0 CRAN (R 4.4.0)
R.utils 2.12.3 CRAN (R 4.4.0)
R6 2.5.1 CRAN (R 4.4.0)
ragg 1.3.3 CRAN (R 4.4.2)
RColorBrewer 1.1-3 CRAN (R 4.4.0)
Rcpp 1.0.13 CRAN (R 4.4.1)
readr 2.1.5 CRAN (R 4.4.0)
remotes 2.5.0 CRAN (R 4.4.0)
rio 1.1.1 CRAN (R 4.4.0)
rlang 1.1.4 CRAN (R 4.4.0)
rmarkdown 2.29 CRAN (R 4.4.2)
rstudioapi 0.17.1 CRAN (R 4.4.2)
rsvg 2.6.0 CRAN (R 4.4.0)
scales 1.3.0 CRAN (R 4.4.0)
sessioninfo 1.2.2 CRAN (R 4.4.0)
shiny 1.9.1 CRAN (R 4.4.2)
showtext 0.9-7 CRAN (R 4.4.1)
showtextdb 3.0 CRAN (R 4.4.1)
snakecase 0.11.1 CRAN (R 4.4.0)
stringi 1.8.4 CRAN (R 4.4.0)
stringr 1.5.1 CRAN (R 4.4.0)
survey 4.4-2 CRAN (R 4.4.0)
survival 3.5-8 CRAN (R 4.4.0)
svglite 2.1.3 CRAN (R 4.4.1)
sysfonts 0.8.9 CRAN (R 4.4.1)
systemfonts 1.1.0 CRAN (R 4.4.0)
tableone 0.13.2 CRAN (R 4.4.0)
textshaping 0.4.0 CRAN (R 4.4.0)
tibble 3.2.1 CRAN (R 4.4.0)
tidyr 1.3.1 CRAN (R 4.4.0)
tidyselect 1.2.1 CRAN (R 4.4.0)
tidyverse 2.0.0 CRAN (R 4.4.0)
timechange 0.3.0 CRAN (R 4.4.0)
tzdb 0.4.0 CRAN (R 4.4.0)
urlchecker 1.0.1 CRAN (R 4.4.0)
usethis 3.1.0 CRAN (R 4.4.2)
utf8 1.2.4 CRAN (R 4.4.0)
V8 4.4.2 CRAN (R 4.4.0)
vctrs 0.6.5 CRAN (R 4.4.0)
viridisLite 0.4.2 CRAN (R 4.4.0)
visNetwork 2.1.2 CRAN (R 4.4.0)
withr 3.0.2 CRAN (R 4.4.2)
writexl 1.5.0 CRAN (R 4.4.0)
xfun 0.49 CRAN (R 4.4.2)
xml2 1.3.6 CRAN (R 4.4.0)
xtable 1.8-4 CRAN (R 4.4.0)
yaml 2.3.10 CRAN (R 4.4.1)
zoo 1.8-12 CRAN (R 4.4.0)